/**
 * 数据处理工具
 * @author luguoxiang
 * @date 2021-09-07
 * 开源项目：https://gitee.com/lgx1992/lg-soar 求star！请给我star！请帮我点个star！
 */
// @ts-ignore
import {dateParse} from './format';

/**
 * 一天的秒数
 */
export const DAY_SECONDS = 24 * 3600;

/**
 * 一天的毫秒数
 */
export const DAY_MS = DAY_SECONDS * 1000;

/**
 * 一年的天数
 */
export const YEAR_DAYS = 365.25;

/**
 * 一年的秒数
 */
export const YEAR_SECONDS = DAY_SECONDS * YEAR_DAYS;

/**
 * 一年的毫秒数
 */
export const YEAR_MS = YEAR_SECONDS * 1000;
function Base64(this: any) {
		// private property
		var _keyStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";

		// public method for encoding
		this.encode = function (input: any) {
				var output = "";
				var chr1, chr2, chr3, enc1, enc2, enc3, enc4;
				var i = 0;
				input = _utf8_encode(input);
				while (i < input.length) {
						chr1 = input.charCodeAt(i++);
						chr2 = input.charCodeAt(i++);
						chr3 = input.charCodeAt(i++);
						enc1 = chr1 >> 2;
						enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
						enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
						enc4 = chr3 & 63;
						if (isNaN(chr2)) {
								enc3 = enc4 = 64;
						} else if (isNaN(chr3)) {
								enc4 = 64;
						}
						output = output +
								_keyStr.charAt(enc1) + _keyStr.charAt(enc2) +
								_keyStr.charAt(enc3) + _keyStr.charAt(enc4);
				}
				return output;
		}

		// public method for decoding
		this.decode = function (input: any) {
				var output = "";
				var chr1, chr2, chr3;
				var enc1, enc2, enc3, enc4;
				var i = 0;
				input = input.replace(/[^A-Za-z0-9\+\/\=]/g, "");
				while (i < input.length) {
						enc1 = _keyStr.indexOf(input.charAt(i++));
						enc2 = _keyStr.indexOf(input.charAt(i++));
						enc3 = _keyStr.indexOf(input.charAt(i++));
						enc4 = _keyStr.indexOf(input.charAt(i++));
						chr1 = (enc1 << 2) | (enc2 >> 4);
						chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
						chr3 = ((enc3 & 3) << 6) | enc4;
						output = output + String.fromCharCode(chr1);
						if (enc3 != 64) {
								output = output + String.fromCharCode(chr2);
						}
						if (enc4 != 64) {
								output = output + String.fromCharCode(chr3);
						}
				}
				output = _utf8_decode(output);
				return output;
		}

		// private method for UTF-8 encoding
		var _utf8_encode = function (string: any) {
				string = string.replace(/\r\n/g,"\n");
				var utftext = "";
				for (var n = 0; n < string.length; n++) {
						var c = string.charCodeAt(n);
						if (c < 128) {
								utftext += String.fromCharCode(c);
						} else if((c > 127) && (c < 2048)) {
								utftext += String.fromCharCode((c >> 6) | 192);
								utftext += String.fromCharCode((c & 63) | 128);
						} else {
								utftext += String.fromCharCode((c >> 12) | 224);
								utftext += String.fromCharCode(((c >> 6) & 63) | 128);
								utftext += String.fromCharCode((c & 63) | 128);
						}

				}
				return utftext;
		}

		// private method for UTF-8 decoding
		var _utf8_decode = function (utftext: any) {
				var string = "";
				var i = 0;
				var c = 0;
				var c1 = 0;
				var c2 = 0;
				var c3;
				while ( i < utftext.length ) {
						c = utftext.charCodeAt(i);
						if (c < 128) {
								string += String.fromCharCode(c);
								i++;
						} else if((c > 191) && (c < 224)) {
								c2 = utftext.charCodeAt(i+1);
								string += String.fromCharCode(((c & 31) << 6) | (c2 & 63));
								i += 2;
						} else {
								c2 = utftext.charCodeAt(i+1);
								c3 = utftext.charCodeAt(i+2);
								string += String.fromCharCode(((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63));
								i += 3;
						}
				}
				return string;
		}
}

// @ts-ignore
export let base64 = new Base64();

export function toBase64(input: string) {
    let b64pad = "";
    const tab: string = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
    let output = "";
    const len = input.length;
    for (let i = 0; i < len; i += 3) {
        const triplet = (input.charCodeAt(i) << 16) | (i + 1 < len ? input.charCodeAt(i + 1) << 8 : 0) | (i + 2 < len ? input.charCodeAt(i + 2) : 0);
        for (let j = 0; j < 4; j++) {
            if (i * 8 + j * 6 > input.length * 8) output += b64pad;
            else output += tab.charAt((triplet >>> 6 * (3 - j)) & 0x3F);
        }
    }
    return output;
}

export function toHex(input: string, hexcase: boolean) {
    const hex_tab = hexcase ? "0123456789ABCDEF" : "0123456789abcdef";
    let output = "";
    let x;
    for (let i = 0; i < input.length; i++) {
        x = input.charCodeAt(i);
        output += hex_tab.charAt((x >>> 4) & 0x0F) + hex_tab.charAt(x & 0x0F);
    }
    return output;
}

/**
 * 转换为布尔值
 * @param value
 */
export function toBoolean(value: any): boolean {
  if (typeof value === 'boolean') {
    return value;
  }
  if (value === 'false') {
    return false;
  }
  if (value === 'true') {
    return true;
  }
  return !!value;
}

/**
 * 转换为布尔值数组
 * @param value
 * @param separator 分隔符
 */
export function toBooleanArray(value: any, separator = ','): boolean[] {
  if (typeof value === 'string') {
    value = value.split(separator);
  }
  if (!Array.isArray(value)) {
    value = [value];
  }
  return value.map(toBoolean);
}

/**
 * 转换为数值
 * @param value
 */
export function toNumber(value: any): number {
  if (typeof value === 'number') {
    return value;
  }
  if (isNaN(value)) {
    throw new Error(`不能转换 ${value} 为数值`);
  }
  return Number(value);
}

/**
 * 转换为数值数组
 * @param value
 * @param separator 分隔符
 */
export function toNumberArray(value: any, separator = ','): number[] {
  if (typeof value === 'string') {
    value = value.split(separator);
  }
  if (!Array.isArray(value)) {
    value = [value];
  }
  return value.map(toNumber);
}

/**
 * 转换为字符串数组
 * @param value
 * @param separator 分隔符
 */
export function toStringArray(value: any, separator = ','): string[] {
  if (typeof value === 'string') {
    return value.split(separator);
  }
  if (!Array.isArray(value)) {
    value = [value];
  }
  return value.map(String);
}

/**
 * 转换为数组
 * @param value
 */
export function toArray(value: any): string[] {
  if (Array.isArray(value)) {
    return value;
  }
  return [value];
}

/**
 * 转换为 Promise
 * @param value
 */
export function toPromise(value: any): Promise<any> {
  if (value instanceof Promise) {
    return value;
  }
  return Promise.resolve(value);
}

/**
 * 转换为 Promise 数组
 * @param value
 */
export function toPromiseArray(value: any): Promise<any>[] {
  if (!Array.isArray(value)) {
    value = [value];
  }
  return value.map(toPromise);
}

interface Option {
  value: any;
  label: string;
  [propName: string]: any;
}
/**
 * 转换数据为 { value: any, label: string }[] 数据
 * @param data
 */
export function toOptions(data: object | string): Option[] {
  const options: Option[] = [];
  if (!data) {
    return options;
  }
  if (typeof data === 'string') {
    data = data.split(',');
  }
  if (Array.isArray(data)) {
    for (let i = 0; i < data.length; i++) {
      // @ts-ignore
      const item: any = data[i];
      // 对象参数
      if (typeof item === 'object') {
        options.push(item);
        continue;
      }
      // 字符串参数
      if (typeof item === 'string') {
        const index: number = item.indexOf(':');
        if (index !== -1) {
          // 字符串参数，使用“:”前面作为值
          options.push({ value: item.substring(0, index), label: item.substring(index + 1) });
          continue;
        }
      }
      // 字符串参数，索引作为值
      options.push({ value: i, label: item });
    }
  } else {
    // 对象参数，key作为值，value做label
    for (const v in data) {
      // @ts-ignore
      options.push({ value: v, label: data[v] });
    }
  }
  return options;
}

/**
 * 数据转为{ [string]: string }
 * @param data
 */
export function toLabelMap(data: object | string) {
  if (!data) {
    return {};
  }
  if (typeof data === 'string') {
    data = data.split(',').map((x: string) => {
      const index: number = x.indexOf(':');
      if (index === -1) {
        return x;
      }
      return { value: x.substring(0, index), label: x.substring(index + 1) };
    });
  }
  if (Array.isArray(data)) {
    const map: any = {};
    for (const i in data) {
      const item = data[i];
      if (typeof item === 'string') {
        const index: number = item.indexOf(':');
        if (index === -1) {
          map[i] = item;
        } else {
          map[item.substring(0, index)] = item.substring(index + 1);
        }
      } else if (typeof item === 'object') {
        map[item.value] = item.label;
      } else {
        map[i] = item;
      }
    }
    return map;
  }
  return data;
}

export type Getter = string | ((d: any) => any);
/**
 * 字段转get方法
 * @param field
 */
export function toGetter(field: Getter) {
    if (typeof field === 'function') {
        return field;
    }
    return (d: any) => d[field];
}

export type Setter = string | ((d: any, value: any) => void);
/**
 * 字段转set方法
 * @param field
 */
export function toSetter(field: Setter) {
    if (typeof field === 'function') {
        return field;
    }
    return (d: any, value: any) => {
        d[field] = value;
    };
}

/**
 * 生成排序器
 * @param sorter 排序方式
 */
export function toSorter(sorter: string | string[] | Function): Function {
  if (typeof sorter === 'function') {
    return sorter;
  }
  // 生成排序器
  const ls: Function[] = toStringArray(sorter).map((x) => {
    if (x.indexOf('-') === 0) {
      x = x.substring(1);
      return (a: any, b: any) => b[x] - a[x];
    }
    return (a: any, b: any) => a[x] - b[x];
  });
  // 完成排序
  return (a: any, b: any): number => {
    let result: number;
    for (const _sorter of ls) {
      result = _sorter(a, b);
      if (result !== 0) {
        return result;
      }
    }
    return 0;
  };
}

/**
 * 数组转map数据
 * @param list
 * @param key
 * @param value
 */
export function toMap(list: any[], key: string | Function, value?: string | Function) {
  const getKey = typeof key === 'string' ? (x: any) => x[key] : key;
  const getValue = value ? (typeof value === 'string' ? (x: any) => x[value] : value) : (x: any) => x;
  const map: any = {};
  for (let i = 0; i < list.length; i++) {
    const x = list[i];
    map[getKey(x, i)] = getValue(x, i);
  }
  return map;
}

/**
 * 数组转树形数据
 * @param {Array} data 源数据
 * @param {String|function} idKey    id字段
 * @param {String|function} pidKey   父id字段
 * @param {String|function} childrenKey   子级字段
 * @returns {Array}  分组后
 */
export function toTreeData(
    data: any[],
    idKey: Getter = 'id',
    pidKey: Getter = 'pId',
    childrenKey: Setter = 'children',
) {
    const getId = toGetter(idKey);
    const getPid = toGetter(pidKey);
    const setChildren = toSetter(childrenKey);

    const map: { [k: string]: any[] } = {};
    for (const x of data) {
        const pid = getPid(x);
        if (pid in map) {
            map[pid].push(x);
        } else {
            map[pid] = [x];
        }
    }
    for (const x of data) {
        const id = getId(x);
        setChildren(x, map[id]);
        delete map[id];
    }

    let res: any[] = [];
    for (const k in map) {
        res = res.concat(map[k]);
    }
    return res;

    // 递归法
    /* function findParent(parent: any) {
      const pid = parent && getId(parent);
      const sameparent: any[] = [];

      for (const a of data) {
        const apid: any = getPid(a);
        if ((!apid && !pid) || apid === pid) {
          const children = findParent(a);
          if (children.length > 0) {
            a[childrenKey] = children;
          }
          a.parent = parent;
          sameparent.push(a);
        }
      }
      return sameparent;
    }
    return findParent({}); */
}

/**
 * 层次数据转扁平数据
 * @param tree 层次数据（树形数据）
 * @param childrenKey 子级字段或获取子级的方法
 */
export function toList(tree: any[], childrenKey: Getter = 'children') {
    const getChildren = toGetter(childrenKey);
    const res: any[] = [];
    function ss(list: any[]) {
        for (const t of list) {
            res.push(t);
            const children = getChildren(t);
            if (children?.length) {
                ss(children);
            }
        }
    }
    ss(tree);

    return res;
}

/**
 * 遍历树形数据
 * @param {Array} list 源数据
 * @param {Function} consumer 回调方法
 * @param {String|function} childrenKey 子数组字段
 * @returns {Array}  分组后
 */
export function forEach(list: any[], consumer: Function, childrenKey: Getter = 'children') {
    const getChildren = toGetter(childrenKey);
    const rs: any[] = [];
    function call(ls: any[], parent: any) {
        if (!Array.isArray(ls)) {
            return;
        }
        for (const x of ls) {
            if (consumer(x, parent)) {
                rs.push(x);
            }
            call(getChildren(x), x);
        }
    }
    call(list, {});
    return rs;
}

/**
 * 转换
 * @param list
 * @param converter
 * @param childrenKey
 * @returns {[]}
 */
export function map(list: any[], converter: Function, childrenKey = 'children') {
    function call(ls: any[], newParent: any, oldParent: any) {
        if (!Array.isArray(ls)) {
            return ls;
        }
        const rs = [];
        for (const x of ls) {
            const data = converter(x, newParent, oldParent);
            data[childrenKey] = call(x[childrenKey], data, x);
            rs.push(data);
        }
        return rs;
    }
    return call(list, {}, {});
}

/**
 * 过滤
 * @param list
 * @param predicate
 * @param childrenKey
 * @returns {[]}
 */
export function filter(list: any[], predicate: Function, childrenKey = 'children') {
    function call(ls: any[], parent: any) {
        if (!Array.isArray(ls)) {
            return ls;
        }
        const rs = [];
        for (const x of ls) {
            const t = { ...x };
            t[childrenKey] = call(t[childrenKey], t);
            if (predicate(t, parent)) {
                rs.push(t);
            }
        }
        return rs;
    }
    return call(list, {});
}

/**
 * 查找元素
 * @param list 树形数据
 * @param predicate 条件判断
 * @param childrenKey 子级字段或函数
 */
export function find(list: any[], predicate: Function, childrenKey: Getter = 'children'): any {
    const getChildren = toGetter(childrenKey);
    function call(ls: any[], parent: any) {
        if (!Array.isArray(ls)) {
            return;
        }
        for (const x of ls) {
            if (predicate(x, parent)) {
                return x;
            }
            const result: any = call(getChildren(x), x);
            if (result) {
                return result;
            }
        }
    }
    return call(list, {});
}

/**
 * 查找上级，根据子级查询所有上级（结果：[子级,...中间级,顶级]）
 * @param tree 树形数据
 * @param predicate 判断方法
 * @param childrenKey 下级数组字段
 */
export function findLinkR(tree: any[], predicate: Function, childrenKey: Getter = 'children') {
    const getChildren = toGetter(childrenKey);
    const list: any[] = [];
    // eslint-disable-next-line consistent-return
    function call(data: any) {
        if (!Array.isArray(data)) {
            return false;
        }
        for (const x of data) {
            if (predicate(x) || call(getChildren(x))) {
                list.push(x);
                return true;
            }
        }
    }
    call(tree);

    return list;
}

/**
 * 查找上级，根据子级查询所有上级（结果：[顶级,...中间级,子级]）
 * @param tree 树形数据
 * @param predicate 判断方法
 * @param childrenKey 下级数组字段
 * @returns {*[]}
 */
export function findLink(tree: any[], predicate: Function, childrenKey: Getter = 'children'): any[] {
    const list = findLinkR(tree, predicate, childrenKey);
    return list.reverse();
}

/**
 * 搜索（符合条件的数据返回数据和数据上下级）
 * @param tree 树数据
 * @param predicate 搜索条件
 * @param childrenKey 子级字段
 */
export function query(tree: any[], predicate: Function, childrenKey = 'children') {
    function call(data: any) {
        if (!Array.isArray(data)) {
            return [];
        }
        const res = [];
        for (const t of data) {
            if (predicate(t)) {
                res.push(t);
                continue;
            }
            let children = t[childrenKey];
            if (children) {
                children = call(children);
                if (children.length) {
                    t[childrenKey] = children;
                    res.push(t);
                }
            }
        }
        return res;
    }
    return call(tree);
}

/**
 * 排序
 * @param list
 * @param sorter
 * @param {string|function}childrenKey
 */
export function sort(list: any[], sorter: string | string[] | Function, childrenKey?: Getter) {
    // eslint-disable-next-line no-underscore-dangle
    const _sorter = toSorter(sorter);
    if (!childrenKey) {
        // @ts-ignore
        list.sort(_sorter);
        return;
    }
    // 树形数据处理
    const getChildren = toGetter(childrenKey);
    function call(ls: any[]) {
        if (!Array.isArray(ls)) {
            return;
        }
        // @ts-ignore
        ls.sort(_sorter);
        for (const x of ls) {
            call(getChildren(x));
        }
    }
    call(list);
}

/**
 * 数租去重
 * @param {Array} list 数据源
 * @param {String，Function} fields 去重对比字段
 * @returns {Array}
 */
export function distinct(list: any[] = [], ...fields: string[]): Array<any> {
  if (!Array.isArray(list)) {
    return list;
  }
  let genKey = (e: any) => {
    let key = '';
    for (const k of fields) {
      key += e[k];
    }
    return key;
  };
  if (fields.length === 0) {
    genKey = (e) => JSON.stringify(e);
  } else if (fields.length === 1 && typeof fields[0] === 'function') {
    genKey = fields[0];
  }
  const map: any = {};
  const result: any[] = [];
  for (const e of list) {
    const key = genKey(e);
    if (map[key]) {
      continue;
    }
    map[key] = true;
    result.push(e);
  }
  return result;
}

/**
 * 数据分组
 * @param list 源数据
 * @param classifier 分组函数，函数返回值作为分组依据
 */
export function grouping(list: any[], classifier: (item: any, i: number) => any) {
  const group: { [p: string]: any[] } = {};
  for (let i = 0; i < list.length; i++) {
    const item = list[i];
    const k = classifier(item, i);
    const d = group[k];
    if (!d) {
      group[k] = [item];
    } else {
      d.push(item);
    }
  }
  return group;
}

/**
 * 深拷贝
 * @param data 数据
 * @param functional 是否复制函数
 */
export function deepCopy(data: any, functional?: boolean): any {
  if (functional) {
    function handler(d: any) {
      if (d instanceof Date) {
        return new Date(d);
      }
      if (Array.isArray(d)) {
        const result: any[] = [];
        for (const x in d) {
          result[x] = handler(d[x]);
        }
        return result;
      }
      if (typeof d === 'object') {
        const result: any = {};
        for (const x in d) {
          result[x] = handler(d[x]);
        }
        return result;
      }
      return d;
    }
    return handler(data);
  }
  return JSON.parse(JSON.stringify(data));
}

/**
 * 比较两个变量是否相等
 * @param a
 * @param b
 */
export function equals(a: any, b: any): any {
  // 值或引用值比较
  if (a === b) {
    return true;
  }
  // 对象或数组比较
  if (a && b && typeof a === 'object' && typeof b === 'object') {
    if (Array.isArray(a)) {
      // 数组长度比较
      if (a.length !== b.length) {
        return false;
      }
    } else {
      // 对象键数量比较
      if (Object.keys(a).length !== Object.keys(b).length) {
        return false;
      }
    }
    // 递归处理嵌套值
    for (const x in a) {
      if (equals(a[x], b[x])) {
        continue;
      }
      return false;
    }
    return true;
  }
  return false;
}

/**
 * 字符串转字节数组
 * @param str
 * @returns {[]}
 */
export function stringToBytes(str: string): number[] {
  const bytes = [];
  const len = str.length;
  for (let i = 0; i < len; i++) {
    bytes.push(str.charCodeAt(i) & 0xff);
  }
  return bytes;
}

/**
 * 生日转年龄
 * @param birthday
 */
export function toAge(birthday: string | Date | number) {
  const diff = new Date().getTime() - dateParse(birthday).getTime();
  return diff / (1000 * 3600 * 24 * 365.25);
}

/**
 * 年龄转生日
 * @param age
 */
export function toBirthday(age: number) {
  return new Date(new Date().getTime() - 1000 * 3600 * 24 * 365.25 * age);
}

const Func = Function;
/**
 * json解析
 * @param jsonStr
 */
export function parseJson(jsonStr: string) {
  return new Func(`return ${jsonStr}`)();
}

/**
 * 是否符合json语法
 * @param data
 */
export function isJson(data: string) {
  try {
    parseJson(data);
    return true;
  } catch (e) {
    return false;
  }
}

/**
 * 数组删除（支持删除多个符合条件的元素）
 * @param list 数组
 * @param predicate 判断函数 或 元素值
 */
export function splice(list: any[], predicate: ((x: any, i: number) => boolean) | any) {
    const func = typeof predicate === 'function' ? predicate : (x: any) => x === predicate;
    for (let i = list.length - 1; i > -1; i--) {
        const x = list[i];
        if (func(x, i)) {
            list.splice(i, 1);
        }
    }
}

/**
 * 解析url参数
 * @param url
 */
export function parseUrlParams(url: string) {
    // @ts-ignore
    url = url.split('?');
    url = url[url.length - 1];
    const params = url.split('&');
    const paramsObj: any = {};
    for (const param of params) {
        const i = param.indexOf('=');
        if (i === -1) {
            continue
        }
        paramsObj[param.substring(0, i)] = param.substring(i + 1);
    }
    return paramsObj;
}


function arrayToUrlParams(name: string, arr: any[]) {
    let flag = true;
    for (const x of arr) {
        // 判断是否是复杂数据，复杂数据不支持省流量处理
        if (typeof x === 'object' || (typeof x === 'string' && x.includes(','))) {
            flag = false;
            break;
        }
    }
    // 转为：name=v1,v2,...vn
    if (flag) {
        return `${name}=${arr.join(',')}`
    }
    // 转为：name=v1&name=v2&...name=vn
    const strs = [];
    for (const x of arr) {
        strs.push(`${name}=${x}`)
    }
    return strs.join('&')
}
/**
 * 转为url参数
 * @param data
 */
export function toUrlParams(data: any): string {
    if (!data) {
        return '';
    }
    const params = JSON.parse(JSON.stringify(data));
    const strs = [];
    for (const name in params) {
        const val = params[name];
        if (val === null || val === undefined) {
            continue;
        }
        if (Array.isArray(val)) {
            strs.push(arrayToUrlParams(name, val))
            continue;
        }
        strs.push(`${name}=${val}`)
    }
    return strs.join('&');
}